色彩空间、HDR和ACES
色彩模型规定了颜色能够被哪些分量(也就是原色)进行表示。举例来说RGB色彩模型下的颜色就可以用RGB三原色的数值来进行表示,比如存在一个颜色可以表示为[Red = 0.2, Green = 0.75, Blue = 0.13]。
常见的色彩模型有RGB、CMYK、HSV、HSL等。
色彩空间如其名,是颜色的空间。由于我们是进行设计、创作、游戏等,最终都会使用显示器、屏幕等进行显示,而这些设备一般都是使用RGB色彩模型,因此此文章只讨论RGB色彩模型相关的问题。
CIE XYZ颜色空间之前有一个CIE RGB颜色空间,CIE XYZ颜色空间中的XYZ是对之前CIE RGB颜色空间的一个修改,因为之前CIE RGB颜色空间不能表达出人眼可见的所有颜色(因为此时的三原色刺激值中的红色部分有负值)。XYZ近似对应红绿蓝。1(https://zhuanlan.zhihu.com/p/137639368)
下图为CIE xy 色度图(Chromaticity Diagram),之所以只用xy是因为,任何可见光颜色都可以通过XYZ三个分量进行一定比例混合,且x + y + z = 1,因此z = 1 - x - y。下图三角形区域表示原来的CIE RGB颜色空间。

CIE XYZ颜色空间可以表达所有的可见颜色(上图的奇怪蹄状三角区域就是人眼可见光区域,但是XYZ能够表达式的范围显然不至于此),是绝对颜色空间,因此常常用这个CIE xy色度图来度量其他颜色空间的色域(Gamut),这种表示方法也叫三色刺激值(Tristimulus Value)表示,也称为色度(Chromaticity)表示。
然后这个CIE XYZ颜色空间和CIE xy色度图很哈人的一点就是,色度图上两点之间的中间位置,完全符合人眼感知的,两颜色的中间值。毕竟是根据人眼感知特性来创造的颜色空间。
RGB色彩模型下有很多色彩空间,常见的有sRGB、Adobe RGB、ProPhoto RGB、DCI-P3、Rec.2020、Rec.709、ACES2065-1、ACEScc/ACEScct、ACEScg。
白点是一个色彩空间里设定为白色的点,用开尔文或者色度来表示。原色就是一个色彩空间的基本颜色,其他颜色都可以通过这些基本颜色进行表示,也用色度表示。每个色彩空间有不同的原色、白点。2(https://www.pcmag.com/encyclopedia/term/white-point)
以DCI-P3 D65色彩空间为例(D65表示其白点为6500开尔文色温的颜色),其白点为(0.3127, 0.3290),原色为R = (0.68, 0.32),G = (0.265, 0.650),B = (0.15, 0.06)。
另外,RGB色彩模式下的色彩空间,在CIE xy色度图上的表示显然都是三角形区域,每一个顶点表示了一个原色,因此其能够表达的颜色区域自然只能是三原色围起来的三角形区域。
以sRGB为例,由于sRGB色彩空间提出的时代,流行的CRT显示器显示图片时,实际发出的亮度和电信号(也就是颜色数据)表示的亮度不是线性的,而是有一个2.2次幂的非线性关系,因此需要提前对图像数据进行一个1 / 2.2 = 0.4545的一个幂次变换,此过程称为伽马校正,校正过后的图像数据处在伽马空间中。但是其实sRGB并不只是一个单纯的伽马校正,是个接近1 / 2.2的分段函数。
现代的显示器其电压亮度响应函数其实是可调的,但是现代显示器一般都支持这个2.2次幂的传统,因此现在sRGB还是非常的常用。
然后Rec.709色彩空间也是一个伽马空间,通常认为它的伽马值为2.4,但实际上也是个分段函数。Rec.709和sRGB其实色域差不多。
显而易见的,对一个颜色空间应用伽马校正,会变成另一个颜色空间,但是这两个颜色空间的色域是不变的,因为应用幂次变换后显然原色和白点都不会变。
DCI-P3蛮怪的,它的色温是6300开尔文,但不是在CIE Standard Illuminant下的,所以不能称为63D,而且白点稍微有点发绿,好怪。然后它是一个系数为2.6的伽马空间,这是为了能适当兼容sRGB。但是DCI-P3色域比sRGB大了有30%,因此许多HDR设备都用这个色彩空间,比如你的手机。
Display-P3是苹果创造的色彩空间,要正常一点。它使用65D的白点,系数为2.2的伽马空间,非常兼容sRGB,感觉还不错。
Rec.2020又比上面两个P3的色域大上一圈,色域相当不错,但是显然造价更高,在激光影院可能会使用。
实际上几个HDR格式HDR10、HDR10+、Dolby Vision、HLG的技术极限都是Rec.2020,普遍的色彩空间都是P3-D65(一个和DCI-P3与Display-P3都不同的P3色域)。
Adobe RGB和DCI-P3的色域面积是差不多的,然而是两个差别不小的色彩空间,所以说在选购显示器的时候不要只关注色域的面积,主要看它的色度图以及和你想要的色彩空间的覆盖率。
sRGB和Rec. 709色域蛮窄,覆盖了色度图35.9%左右区域4(https://zhuanlan.zhihu.com/p/138295496),使用它作为色彩空间的OO称为SDR(Standard Dynamic Range)。相对的HDR的色域就蛮高。下面是一些HDR格式的参数5(https://en.wikipedia.org/wiki/High-dynamic-range_television)。
| HDR10 | HDR10+ | Dolby Vision | HLG | |
|---|---|---|---|---|
| Developed by | CTA | Samsung | Dolby | NHK and BBC |
| Year | 2015 | 2017 | 2014 | 2015 |
| Cost | Free | Free (for content company)Yearly license (for manufacturer) 42\(https://en.wikipedia.org/wiki/High-dynamic-range_television#cite_note-42) | Proprietary | Free |
| Transfer function | PQ | PQ | PQ (in most profiles)43\(https://en.wikipedia.org/wiki/High-dynamic-range_television#cite_note-:332-43)SDR (in profiles 4, 8.2, and 9)43\(https://en.wikipedia.org/wiki/High-dynamic-range_television#cite_note-:332-43)HLG (in profile 8.4)43\(https://en.wikipedia.org/wiki/High-dynamic-range_television#cite_note-:332-43) | HLG |
|---|---|---|---|---|
| Bit Depth | 10 bit | 10 bit (or more) | 10 bit (or 12 bit using FEL)note 1\(https://en.wikipedia.org/wiki/High-dynamic-range_television#cite_note-45) | 10 bit |
HDR10、HDR10+、Dolby Vision都是使用PQ传输函数(Transfer Function)。
这些HDR格式,由于色域比sRGB提高了不少,因此也需要用更多的比特来精确地表示颜色(说实话,Rec.2020的色域覆盖了75.8%5(https://en.wikipedia.org/wiki/Rec._2020)的CIE 1931色彩空间,感觉用16bit的bpc才勉强能行),因此HDR都是10比特起步的。
也有人翻译为转换函数,实际上就是起到一个转换的作用。
传输函数分为OETF(opto-electronic transfer function,光电传输函数)、EOTF(electro-optical transfer function,电光传输函数)、OOTF(opto-optical transfer function,光光传输函数)
OOTF说是OETF除以EOTF的结果,可能是用来衡量看到的和拍摄的之间失真度的东西。
OETF是从摄像机等输入设备到图像数据的转换函数,在摄像机内完成,我不太想研究这部分。
EOTF是从图像数据到光信号的转换函数,这是在显示器内完成的,例如前面说到的sRGB和Rec.709,就需要先进行一次伽马校正,这样在经过EOTF之后才是显示的正常的光。大家都喜欢的HDR10、HDR10+、Dolby Vision在设备上会进行PQ转换,所以应该要在此之前进行反PQ编码,HLG同理,不过是HLG转换和反HLG编码。 SDR的色彩范围始终是0到1的,这样在不同亮度峰值的显示器上的亮度是不同的7(https://zhuanlan.zhihu.com/p/144775352)。而HDR的这些EOTF,以PQ为例,实现了0到10000尼特的转换,因此在不同显示器上的亮度也应该是一样的如果显示器支持的话。
微软有在文档里写普通的颜色变换管线和Windows实现任意两个色彩空间转换的过程6(https://learn.microsoft.com/en-us/windows/win32/wcs/display-calibration-mhc)。
首先0是原本的色彩空间,首先如果这个色彩空间不是线性空间,则需要先进行反伽马变换1,变换完后就到了线性空间,线性空间的颜色就可以通过颜色矩阵2这种线性变换来变换到其他颜色空间,如果目标颜色空间也是一个伽马空间,则要施加一个对应的伽马校正3,然后就得到了转换后的颜色空间。

Windows使用单独的管线。其实这个例子都不太像是颜色变换管线而是图像显示管线了

介绍一下各个步骤:
其实还有第0步,那就是Framebuffer里面渲染得到的颜色数据,它们都是RGB伽马空间的,可以是sRGB, sYCC, HDR10, 或者scRGB颜色空间。
反伽马,转换到线性空间。
2a、2b、2c是颜色空间转换的三个变换,它们乘在一起就是颜色空间的转换矩阵。
2a:从线性空间转换到CIE XYZ的绝对颜色空间。 2b:做一些例如校准的调整,可编程。 2c:从CIE XYZ转换到目标的线性的RGB色彩空间上。 2a和Framebuffer相关,2c和显示器相关,普通应用可以控制2b这个矩阵。 然后上面的1、2都是在显示器驱动控制下完成的。
也分两步。 3a:使用EOTF来伽马校正,由显示器驱动完成。 3b:做一些例如校准的调整,又是可编程的那种。 3a的转换由wire format color space(实在搜不到)来指定,但应该就是显示器的颜色空间,例如SDR显示器就需要做一次伽马校正,HDR10显示器就需要做一次逆PQ(或称ST.2084)电光传输函数的变换。
完成上述步骤后,Framebuffer(没错,还是在Framebuffer里)里的数据就会扫描到你GPU连接显示器的那条线里,然后显示。
上面其实还不是完整的显示过程,第0步显然也是需要输入的,这部分说明了上文第0步是怎么来的8(https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range)。
先说说Windows的HDR显示,首先HDR是在Windows10 1703才支持的,其步骤如下:

SDR显示就是一坨,不管应用渲染得多精细,SDR显示都会Clip到8bpc。
Apps using graphics APIs such as DirectX could perform internal rendering using high bit-depths and extended color spaces; however, the OS supported only 8-bit integer with implicit sRGB and no system color management
然而......
喜大普奔,Windows 11 22H2及之后,SDR的显示流程也和HDR的类似了,至少来说支持超过8bpc的应用的数据了,超过8bpc的颜转换到CCCS之后会得到正确的结果,这样在10bpc的SDR显示器上能够显示更加丰富的颜色了。

泪目。
如果把游戏引擎的渲染流程纳入考虑,那么Windows的,线性工作流下的游戏引擎渲染流程就是:

色调映射发生在渲染这个过程。
帧缓冲就是用来存需要显示在显示器上数据的显存。在Vulkan API下9(https://github.com/liangz0707/whoimi/blob/master/blogs/GPUAartch/HDR%E7%A1%AC%E4%BB%B6%E5%B1%82Framebuffer%E6%A0%BC%E5%BC%8F%E6%A0%87%E5%87%86%E4%B8%8E%E6%B8%B2%E6%9F%93%E9%98%B6%E6%AE%B5%E7%9A%84%E5%85%B3%E7%B3%BB.md):
对于SDR屏幕,Buffer主要有:
R8G8B8A8_UNORM:首先屏幕的EOTF曲线是始终存在的,这种格式输入的颜色,图像接口不会进行任何处理,需要我们自己在shader中进行sRGB编码。
R8G8B8A8_SNORM:这种格式输入的颜色在显示之前会自动编码到SRGB空间(自动Gamma矫正),所以我们不需要进行处理,也就是说shader当中直接写出线性颜色即可。A通道是线性的。
对于HDR屏幕,Buffer主要有:
R16G16B16_Float:这种格式输出的是线性浮点数颜色,数据可以超过1,1对应的是80nits。使用的是scRGB。
DX的参考:
https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
R10G10B10A2_UNORM:这种格式不会进行任何处理,直接结合EOFT曲线进行显示, 所以需要我们自己在Shader中进行PQ编码。(在我的显示器上Vulkan不支持,暂时没有测试过,但是GL和DX是支持这个格式的)
VK_COLOR_SPACE_HDR10_ST2084_EXT:说明colorSpace,表示使用BT2020颜色空间,使用ST2048的EOTF。
这些图形API输出的帧缓冲应该都是上文Windows的色彩显示流程中APP的输入,由于Windows的色彩显示流程阶段会将图形API输出的帧缓冲根据其色彩空间转换到scRGB或说CCCS,因此我们只需要保证在渲染时,渲染出的图像数据和色彩空间匹配即可,按Vulkan来说就是要让Color Attachment是正确的。
此外,感觉和我一样搞游戏技术的,都会觉得图像API输出的帧缓冲就是天,实际上其他所有窗口都会输出帧缓冲数据,然后交由DWM来合成,合成后的内容也是储存在帧缓冲里,甚至由显示器驱动进行的处理也是存在帧缓冲的——帧缓冲不是什么特别的东西。
上文其实已经解释过了如何从HDR的颜色空间(如P3-D65)转换到SDR的颜色空间(如sRGB),但是简单地从色域较大的空间转换到色域较小的空间,一定会产生SDR中无法表现的颜色,如果直接将超出范围的颜色进行截断,就会出现如大片相同颜色,因此需要在转换之前先进行色调映射。此外HDR图像是浮点表示的,可能会产生值超过1的部分,但SDR是归一化的色彩空间,所以数据上来说,其归一化后的数值只能从0到1,要将HDR图像转换到SDR,就需要色调映射。(个人理解,不知道对不对)
色调映射的算法蛮多,比较常用的有CE Tone Mapping、Filmic Tone Mapping,但现在基本都被ACES色调映射统一了10(https://zhuanlan.zhihu.com/p/21983679)。这篇文章说ACES色调映射是ACES提出的,但是我找不到任何证据(比如ACES官网上查不到任何色调映射相关的文字)说明这是ACES提出,而应该是Epic Games的图程Krzysztof Narkowicz基于ACES的ODT提出的,他在他的博客中有提到11(https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/),在研究出来之后称为了UE的默认色调映射函数,UE的文档[[12]](https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/PostProcessEffects/ColorGrading/)可以看到。

ACES色调映射不仅比较符合感知,细节损失较少,而且代码很简单,开销蛮小,很适合用在游戏里,比如UE4.8及之后就全面支持了ACES色调映射。下面是Krzysztof Narkowicz写的最初版的HLSL代码。
float3 ACESToneMapping(float3 color, float adapted_lum)
{
const float A = 2.51f;
const float B = 0.03f;
const float C = 2.43f;
const float D = 0.59f;
const float E = 0.14f;
color *= adapted_lum;
return (color * (A * color + B)) / (color * (C * color + D) + E);
}
据称,ACES色调映射不仅可以用于HDR到SDR的转换,在你的HDR显示器峰值亮度不够时,还可以进行HDR到HDR的转换。
建议称其为ACES色调映射或者ACES Filmic Tone Mapping,而不是简单的ACES,不然很容易和真正的ACES(学院颜色编码系统)弄混。
中文是学院颜色编码系统,大家都简称为ACES。它的优势在于,给影片制作的全部流程,都提供了一个统一的色彩管理,且由于其较大的色域,并参与了影片从拍摄到放映的全过程,相比传统流程来说质量更好13(https://blog.frame.io/2019/09/09/guide-to-aces/)。

ACES其实是包含了很多部分的14(https://zhuanlan.zhihu.com/p/102701796#ACES%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E7%BB%84%E6%88%90%E9%83%A8%E5%88%86%EF%BC%9F):
- ACES Input Transform(IDT或输入设备转换):它将摄影机独有的拍摄参考数据转换为线性场景的ACES色彩空间。目前,摄影机厂商会为自己的摄影机系统来开发IDT转换算法,然后ACES学院对其进行测试和验证,未来ACES学院会拥有更多的控制权。IDT转换和其它ACES转换一样也是用CTL(色彩转换语言)编程语言来编写的。IDT并不是完全与摄影机系统一对一的关系,有时也会将不同的IDT应用于同一个摄影机系统来补偿一些摄影机系统中因不同设置所带来的差别。
- ACES Look Transform(LMT或外观修改转换):它是ACES观看转换(它由LMT、RRT和ODT系统组成)的一部分,它提供一种类似于将LUT应用于镜头的方法。不同的是,LMT位于ACES色彩的调色流程之后,且并非所有工作都支持它。
- Reference Rendering Transform(RRT或参考渲染转换):你可以将其理解为ACES的渲染引擎组件,RRT将场景参考的线性数据转换为超宽的显示参考数据集。RRT与ODT一起为显示创建可视化的数据。
- ACES Output Transform(ODT或输出设备转换):这是ACES流程的最后一步,它从RRT获得超宽和高动态范围的数据后,转换为不同显示设备所对应的色彩空间,比如P3、Rec.709、Rec.2020等。
ACES的野心不小,它囊括了拍摄、调色、特效、显示、归档各个阶段,流程可以归纳为如下图15(https://www.dropbox.com/s/0xhva7vniipx3zk/TB-2014-004.pdf?dl=0):

其中摄像机部分的流程应当如下图,不管是真实的摄像机还是各种渲染器/渲染引擎的摄像机,都会经过IDT变换变换到ACES2065-1色彩空间:

ACES Central举的一个ACES流程的例子[16]为下图:

可以看到,在摄像期间,摄像机捕获到的自然界的光线会经过OETF先转换成电信号(现在是在摄像机自己的log颜色空间下),然后再经过IDT(Input Device Transform)后转换到ACES2065-1色彩空间。
其实IDT是ACES第一版的称呼,现在改名为Input Transform了17(https://en.wikipedia.org/wiki/Academy_Color_Encoding_System),但好像还是简称IDT。
ACES中规定和使用了几个色彩空间:
ACES2065-1使用AP0的原色,是一个线性空间,主要是为了存储、归档素材18(https://acescentral.com/knowledge-base-2/aces-working-spaces/)而设计的,
也是在ACES中用于进行色彩交换的中间空间,例如说在进行ACEScc转换到P3-D65的过程中,就要先转换到ACES2065-1再转到P3-D65,就类似Windows上使用的scRGB。
ACEScc使用AP1原色,是一个log空间,这个空间可以用于分级调色(Color Grading)中,说是因为有些调色师喜欢在log空间下调色。
ACEScct和ACEScc差不多,只是在趾(Toe)部会有一些抬升。
A variant of ACEScc color space, except that it adds a “toe” to make it more akin to traditional “log” curves to generate a distinct “milking” or “fogging” of shadows under lift operations, which is typical of some film looks. This comes from requests to provide colorists with a grading environment more similar to that of traditional legacy log film scan encodings when grading using a working space from the “ACES” family of color spaces.
可能读者会有些疑惑,这个ACEScg是一个线性空间,那岂不是说我们在进行合成时看到的色彩竟然是处在线性空间的,置显示器EOTF于何处!
其实ACES这些色彩空间,都是需要先转换到sRGB、VP-D65等显示器的色彩空间之后再显示的——目前还没有ACES的显示器——上面流程图中的RRT&ODT就是进行了这些处理,以我们用sRGB屏幕来显示ACEScg的内容来说,会有一个ACEScg->ACES2065->sRGB的过程,第一步称为RRT(Reference Rendering Transform),第二步称为ODT(Out Device Transform),这两个过程合称为Output Transform(wikipedia说的,感觉有点怪)。
LMT(Look Modification Transform)是用在ACES色彩空间之间进行转换的步骤,例如在分级调色之前就需要将ACES的某种色彩空间转换到ACEScc或者ACEScct色彩空间中去。
wikipedia上说LMT + RRT + ODT才组成Academy Viewing Transform,但我还不太懂LMT在这里的作用是什么。
注意,读者如果要去阅读ACES的文档,需要理解文档中提到的scene-referred或者说scene-linear是指ACES2065-1,猜测因为它的色彩空间足够大所以称之为“referred”。
伽马、色彩空间、位深度在ACES的每个过程中如下图17(https://community.acescentral.com/t/acescc-vs-acescct/485/21)所示:

本来想再写一点线性工作流和伽马工作流的,不过这个前人应该都叙述比较详细了,就偷懒了,可以参考这篇文章20(https://zhuanlan.zhihu.com/p/100131251)。
本文作者为朵格Dolag,本文使用[CC BY 4.0知识分享协议](Creative Commons Attribution 4.0 International | Choose a License),转载引用需署名。